from myapp.views.baseSQLA import MyappSQLAInterface as SQLAInterface
from myapp.models.model_train_model import Training_Model
from myapp.models.model_dependency import Model_Dependency
from myapp.models.model_serving import InferenceService
from flask_babel import gettext as __
from flask_babel import lazy_gettext as _
from myapp import app, appbuilder, db
import uuid
from myapp.views.view_team import Project_Join_Filter
from myapp.utils import core
from wtforms.validators import DataRequired, Length, Regexp
from wtforms import SelectField, StringField
from flask_appbuilder.fieldwidgets import Select2Widget
from flask_appbuilder.actions import action
from myapp.forms import MyBS3TextFieldWidget, MyBS3TextAreaFieldWidget
from myapp import security_manager
from flask import (
    flash,
    g,
    redirect, request,
    flash,
    g,
    Markup,
    make_response,
    redirect,
    request,
    url_for
)
from .base import (
    DeleteMixin,
    MyappFilter,
    MyappModelView,
)
from .baseApi import (
    MyappModelRestApi
)

from flask_appbuilder import expose
import datetime, json

conf = app.config


class Training_Model_Filter(MyappFilter):
    # @pysnooper.snoop()
    def apply(self, query, func):
        user_roles = [role.name.lower() for role in list(self.get_user_roles())]
        if "admin" in user_roles:
            return query
        join_projects_id = security_manager.get_join_projects_id(db.session)
        return query.filter(self.model.project_id.in_(join_projects_id))
        # return query.filter(self.model.created_by_fk == g.user.id)

from myapp.services.dependency_analyzer import ModelDependencyAnalyzer
class Training_Model_ModelView_Base():
    datamodel = SQLAInterface(Training_Model)
    base_permissions = ['can_add', 'can_edit', 'can_delete', 'can_list', 'can_show']
    base_order = ('changed_on', 'desc')
    order_columns = ['id']
    
    # 新增列
    list_columns = ['project_url', 'name', 'version', 'model_type', 'model_size', 
                   'model_metric', 'framework', 'api_type', 'pipeline_url',
                   'creator', 'modified', 'deploy', 'dependencies_count','military_concept_desc']
    
    add_columns = ['project', 'name', 'version', 'describe', 'path','dll_path', 'framework', 
                  'run_id', 'run_time', 'metrics', 'md5', 'api_type', 'pipeline_id',
                  'model_type', 'parent_model', 'tags', 'training_dataset', 
                  'model_config', 'dependencies','military_concept_desc']
    
    # @property
    # def dependencies_count(self):
    #     """依赖数量"""
    #     count = db.session.query(Model_Dependency).filter_by(source_model_id=self.id).count()
    #     return Markup(f'<span class="badge badge-info">{count}</span>')
    search_columns = ['created_by', 'project', 'name', 'version', 'framework', 'api_type', 'pipeline_id', 'run_id',
                      'path']
    # add_columns = ['project', 'name', 'version', 'describe', 'path', 'framework', 'run_id', 'run_time', 'metrics',
    #                'md5', 'api_type', 'pipeline_id']
    edit_columns = add_columns
    show_columns = ['project_url', 'name', 'version', 'model_type', 'model_size', 
                   'model_metric', 'framework', 'api_type', 'pipeline_url',
                   'creator', 'modified', 'deploy', 'dependencies_count']
     # 添加自定义模板
    show_template = 'model_detail_with_visualization.html'
    
    add_form_query_rel_fields = {
        "project": [["name", Project_Join_Filter, 'org']]
    }
    edit_form_query_rel_fields = add_form_query_rel_fields
    cols_width = {
        "name": {"type": "ellip2", "width": 250},
        "project_url": {"type": "ellip2", "width": 200},
        "pipeline_url": {"type": "ellip2", "width": 300},
        "version": {"type": "ellip2", "width": 200},
        "modified": {"type": "ellip2", "width": 150},
        "deploy": {"type": "ellip2", "width": 100},
        "model_metric": {"type": "ellip2", "width": 300},
        "military_concept_desc": {"type": "ellip2", "width": 300},
    }
    spec_label_columns = {
        "project": _("项目组"),
        "name": _("模型名称"),
        "version": _("版本"),
        "describe": _("描述"),
        "path": _("模型文件"),
        "dll_path": _("动态链接库"),
        "framework": _("训练框架"),
        "run_id": _("运行ID"),
        "run_time": _("运行时间"),
        "metrics": _("指标"),
        "md5": _("MD5值"),
        "api_type": _("推理框架"),
        "pipeline_id": _("任务流ID"),
        "model_type": _("模型类型"),
        "parent_model": _("父模型"),
        "tags": _("标签"),
        "training_dataset": _("训练数据集"),
        "model_config": _("模型配置"),
        "dependencies": _("依赖项"),
        "military_concept_desc": _("军事概念描述"),
        "deploy": _("发布"),
        "creator": _("创建者"),
        "modified": _("修改时间"),
        "model_size": _("模型大小"),
        "model_metric": _("模型指标"),
        "pipeline_url": _("任务流"),
        "project_url": _("项目组"),
        "dependencies_count": _("依赖数量")
    }

    label_title = _('模型')
    base_filters = [["id", Training_Model_Filter, lambda: []]]

    model_path_describe = '''serving：自定义镜像的推理服务，模型地址随意
ml-server：支持sklearn和xgb导出的模型，需按文档设置ml推理服务的配置文件
tfserving：仅支持添加了服务签名的saved_model目录地址，例如：/mnt/xx/../saved_model/
torch-server：torch-model-archiver编译后的mar模型文件，需保存模型结构和模型参数，例如：/mnt/xx/../xx.mar或torch script保存的模型
onnxruntime：onnx模型文件的地址，例如：/mnt/xx/../xx.onnx
triton-server：框架:地址。onnx:模型文件地址model.onnx，pytorch:torchscript模型文件地址model.pt，tf:模型目录地址saved_model，tensorrt:模型文件地址model.plan
vllm: 不同镜像提供不同的推理架构，使用vllm提供gpu推理加速和openai流式接口
'''.strip()

    service_type_choices = [x.replace('_', '-') for x in ['serving','ml-server','tfserving', 'torch-server', 'onnxruntime', 'triton-server','vllm','aihub']]

    add_form_extra_fields = {
        "path": StringField(
            _('模型文件地址'),
            default='/mnt/admin/xx/saved_model/',
            description=_('模型文件的容器地址或下载地址，格式参考详情。<a target="_blank" href="/notebook_modelview/api/entry/jupyter?file_path=/mnt/{{creator}}/">上传模型</a>'),
            validators=[DataRequired()],
            widget=MyBS3TextFieldWidget(tips=_(model_path_describe))
        ),
        "describe": StringField(
            _("描述"),
            description= _('模型描述'),
            validators=[DataRequired()]
        ),
        "pipeline_id": StringField(
            _('任务流id'),
            description= _('任务流的id，0表示非任务流产生模型'),
            default='0'
        ),
        "version": StringField(
            _('版本'),
            widget=MyBS3TextFieldWidget(),
            description= _('模型版本'),
            default=datetime.datetime.now().strftime('v%Y.%m.%d.1'),
            validators=[DataRequired()]
        ),
        "run_id": StringField(
            _('run id'),
            widget=MyBS3TextFieldWidget(),
            description= _('pipeline 训练的run id'),
            default='random_run_id_' + uuid.uuid4().hex[:32]
        ),
        "run_time": StringField(
            _('保存时间'),
            widget=MyBS3TextFieldWidget(),
            description= _('模型的保存时间'),
            default=datetime.datetime.now().strftime('%Y.%m.%d %H:%M:%S'),
        ),
        "name": StringField(
            _("模型名"),
            widget=MyBS3TextFieldWidget(),
            description= _('模型名(a-z0-9-字符组成，最长54个字符)'),
            validators=[DataRequired(), Regexp("^[a-z0-9\-]*$"), Length(1, 54)]
        ),
        "framework": SelectField(
            _('算法框架'),
            description= _("选项xgb、tf、pytorch、onnx、tensorrt等"),
            widget=Select2Widget(),
            choices=[['sklearn','sklearn'],['xgb', 'xgb'], ['tf', 'tf'], ['pytorch', 'pytorch'], ['onnx', 'onnx'], ['tensorrt', 'tensorrt'],['aihub', 'aihub']],
            validators=[DataRequired()]
        ),
        'api_type': SelectField(
            _("部署类型"),
            description= _("推理框架类型"),
            choices=[[x, x] for x in service_type_choices],
            validators=[DataRequired()]
        ),
        "military_concept_desc": StringField(
            _("军事概念描述"),
           description=_('描述该模型在军事领域的应用概念和场景'),
           widget=MyBS3TextAreaFieldWidget(),
        ),
        "dll_path": StringField(
            _("动态链接库地址"),
            default='/mnt/admin/xx/saved_model/dll/',
            description= _('动态链接库文件的容器地址或下载地址。'),
            validators=[DataRequired()]
        )
    }
    edit_form_extra_fields = add_form_extra_fields

    # edit_form_extra_fields['path']=FileField(
    #         __('模型压缩文件'),
    #         description=_(path_describe),
    #         validators=[
    #             FileAllowed(["zip",'tar.gz'],_("zip/tar.gz Files Only!")),
    #         ]
    #     )

    # @pysnooper.snoop(watch_explode=('item'))
    def pre_add(self, item):
        if not item.run_id:
            item.run_id = 'random_run_id_' + uuid.uuid4().hex[:32]
        if not item.pipeline_id:
            item.pipeline_id = 0

    def pre_update(self, item):
        if not item.path:
            item.path = self.src_item_json['path']
        self.pre_add(item)

    # 检测是否具有编辑权限，只有creator和admin可以编辑
    def check_edit_permission(self, item):
        if g.user and g.user.is_admin():
            return True
        if g.user and g.user.username and hasattr(item, 'created_by'):
            if g.user.username == item.created_by.username:
                return True
        # flash('just creator can edit/delete ', 'warning')
        return False
    
    # 添加自定义操作按钮
    @action("view_dependency_graph", "查看依赖图", "", "fa-sitemap", single=True)
    def view_dependency_graph(self, items):
        """查看模型依赖关系图 - 直接返回JSON数据"""
        if not items:
            return self.response(400, message="请选择一个模型")

        model = items[0] if isinstance(items, list) else items

        # 直接返回JSON数据，不再区分API调用和Web调用
        try:
            from myapp.models.model_dependency import Model_Dependency
            from sqlalchemy import or_

            # 查询与该模型相关的所有依赖关系
            dependencies = db.session.query(Model_Dependency).filter(
                or_(Model_Dependency.source_model_id == model.id,
                    Model_Dependency.target_model_id == model.id)
            ).all()

            nodes = []
            edges = []
            node_ids = set()

            # 添加当前模型作为节点
            nodes.append({
                'id': model.id,
                'label': model.name,
                'type': model.model_type or '未知类型',
                'name': model.name,
                'description': model.describe or '',
                'is_current': True
            })
            node_ids.add(model.id)

            # 处理依赖关系
            for dep in dependencies:
                # 添加源模型节点
                if dep.source_model_id not in node_ids:
                    source_model = dep.source_model
                    if source_model:
                        nodes.append({
                            'id': dep.source_model_id,
                            'label': source_model.name,
                            'type': source_model.model_type or '未知类型',
                            'name': source_model.name,
                            'description': source_model.describe or '',
                            'is_current': False
                        })
                        node_ids.add(dep.source_model_id)

                # 添加目标模型节点
                if dep.target_model_id not in node_ids:
                    target_model = dep.target_model
                    if target_model:
                        nodes.append({
                            'id': dep.target_model_id,
                            'label': target_model.name,
                            'type': target_model.model_type or '未知类型',
                            'name': target_model.name,
                            'description': target_model.describe or '',
                            'is_current': False
                        })
                        node_ids.add(dep.target_model_id)

                # 添加边
                edges.append({
                    'source': dep.source_model_id,
                    'target': dep.target_model_id,
                    'type': dep.dependency_type or '未知依赖',
                    'strength': dep.dependency_strength or 1.0,
                    'description': dep.description or '',
                    'created_on': dep.created_on.isoformat() if dep.created_on else None
                })

            # 构建响应数据
            response_data = {
                'nodes': nodes,
                'edges': edges,
                'model_info': {
                    'id': model.id,
                    'name': model.name,
                    'type': model.model_type,
                    'description': model.describe,
                    'created_on': model.created_on.isoformat() if model.created_on else None,
                    'updated_on': model.changed_on.isoformat() if model.changed_on else None
                },
                'statistics': {
                    'total_nodes': len(nodes),
                    'total_edges': len(edges),
                    'dependency_types': list(set([edge['type'] for edge in edges])) if edges else []
                }
            }

            # 直接返回Response对象，这样single_action就不会覆盖我们的返回值
            from flask import Response, jsonify
            return Response(
                response=jsonify({
                    "status": 0,
                    "message": "依赖图数据获取成功",
                    "data": response_data
                }).data,
                status=200,
                mimetype='application/json'
            )

        except Exception as e:
            import traceback
            from flask import Response, jsonify
            error_details = traceback.format_exc()
            print(f"获取依赖图失败: {error_details}")
            return Response(
                response=jsonify({
                    "status": -1,
                    "message": f"获取依赖图失败: {str(e)}",
                    "data": {'error_details': error_details}
                }).data,
                status=500,
                mimetype='application/json'
            )
    
    # 添加依赖图页面方法
    @expose("/dependency_graph/<model_id>", methods=["GET"])
    def dependency_graph_page(self, model_id):
        """模型依赖图页面"""
        model = db.session.query(Training_Model).filter_by(id=model_id).first()
        if not model:
            flash('模型不存在', 'error')
            return redirect(self.get_redirect())
        
        return self.render_template('model_dependency_graph.html', 
                                model=model, 
                                model_id=model_id)
    
    @action("view_version_timeline", "版本时间线", "查看模型版本演进时间线", "fa-clock-o", single=True)
    def view_version_timeline(self, items):
        """查看模型版本时间线"""
        if not items:
            if hasattr(self, 'get_redirect'):
                flash('请选择一个模型', 'warning')
                return redirect(self.get_redirect())
            else:
                return self.response(400, message="请选择一个模型")
        
        model = items[0] if isinstance(items, list) else items
        
        # 重定向到版本时间线页面（Web视图）
        if hasattr(self, 'get_redirect'):
            return redirect(url_for('Training_Model_ModelView.version_timeline', model_id=model.id))
        else:
            # API响应直接返回数据
            return redirect(url_for('Model_Visualization_Api.version_timeline', model_id=model.id))
    
    @expose("/version_timeline/<model_id>", methods=["GET"])
    def version_timeline(self, model_id):
        """模型版本时间线页面"""
        model = db.session.query(Training_Model).filter_by(id=model_id).first()
        if not model:
            flash('模型不存在', 'error')
            return redirect(self.get_redirect())
        
        # 渲染版本时间线页面
        return self.render_template('model_version_timeline.html', 
                                  model=model, 
                                  model_id=model_id)
    
    # @action("batch_view_dependencies", "批量查看依赖", "批量查看模型依赖关系", "fa-sitemap", multiple=True)
    # def batch_view_dependencies(self, items):
    #     """批量查看模型依赖关系"""
    #     if not items:
    #         flash('请选择至少一个模型', 'warning')
    #         return redirect(self.get_redirect())
        
    #     model_ids = ','.join([str(item.id) for item in items])
    #     # 修复：使用自定义方法的endpoint名称
    #     return redirect(url_for(f"{self.endpoint}.batch_dependencies", model_ids=model_ids))
    
    # @expose("/batch_dependencies/<model_ids>", methods=["GET"])
    # def batch_dependencies(self, model_ids):
    #     """批量依赖关系页面"""
    #     ids = model_ids.split(',')
    #     models = db.session.query(Training_Model).filter(Training_Model.id.in_(ids)).all()
        
    #     return self.render_template('model_batch_dependencies.html', 
    #                               models=models, 
    #                               model_ids=model_ids)
        
    # @action("analyze_dependencies", "分析依赖关系", "自动分析模型依赖关系", "fa-search", single=True)
    # def analyze_dependencies(self, items):
    #     """自动分析模型依赖关系"""
    #     if not items:
    #         # 区分API和Web视图的响应方式
    #         if hasattr(self, 'get_redirect'):
    #             flash('请选择一个模型', 'warning')
    #             return redirect(self.get_redirect())
    #         else:
    #             return self.response(400, message="请选择一个模型")
        
    #     model = items[0] if isinstance(items, list) else items
        
    #     try:
    #         analyzer = ModelDependencyAnalyzer()
    #         analyzer.sync_dependencies_to_db(model.id)
            
    #         if hasattr(self, 'get_redirect'):
    #             flash(f'模型 {model.name} 的依赖关系分析完成', 'success')
    #             return redirect(self.get_redirect())
    #         else:
    #             return self.response(200, message=f'模型 {model.name} 的依赖关系分析完成')
    #     except Exception as e:
    #         if hasattr(self, 'get_redirect'):
    #             flash(f'依赖关系分析失败: {str(e)}', 'error')
    #             return redirect(self.get_redirect())
    #         else:
    #             return self.response(500, message=f'依赖关系分析失败: {str(e)}')
            
    # @action("dependency_management", "依赖管理", "管理模型依赖关系", "fa-cogs", single=True)
    # def dependency_management_action(self, items):
    #     """依赖关系管理"""
    #     if not items:
    #         if hasattr(self, 'get_redirect'):
    #             flash('请选择一个模型', 'warning')
    #             return redirect(self.get_redirect())
    #         else:
    #             return self.response(400, message="请选择一个模型")
        
    #     model = items[0] if isinstance(items, list) else items
    #     return redirect(url_for(f"{self.endpoint}.dependency_management", model_id=model.id))
    
    # @action("batch_analyze_dependencies", "批量分析依赖", "批量自动分析模型依赖关系", "fa-search", multiple=True)
    # def batch_analyze_dependencies(self, items):
    #     """批量分析模型依赖关系"""
    #     if not items:
    #         if hasattr(self, 'get_redirect'):
    #             flash('请选择至少一个模型', 'warning')
    #             return redirect(self.get_redirect())
    #         else:
    #             return self.response(400, message="请选择至少一个模型")
        
    #     analyzer = ModelDependencyAnalyzer()
    #     success_count = 0
    #     error_count = 0
        
    #     for model in items:
    #         try:
    #             analyzer.sync_dependencies_to_db(model.id)
    #             success_count += 1
    #         except Exception as e:
    #             error_count += 1
    #             print(f"分析模型 {model.name} 依赖关系失败: {e}")
        
    #     message = f'批量分析完成: 成功 {success_count} 个，失败 {error_count} 个'
    #     if hasattr(self, 'get_redirect'):
    #         flash(message, 'info')
    #         return redirect(self.get_redirect())
    #     else:
    #         return self.response(200, message=message)
    
    @expose("/dependency_management/<model_id>", methods=["GET", "POST"])
    def dependency_management(self, model_id):
        """依赖关系管理页面"""
        model = db.session.query(Training_Model).get(model_id)
        if not model:
            flash('模型不存在', 'error')
            return redirect(self.get_redirect())
        
        if request.method == 'POST':
            # 处理依赖关系的添加、编辑、删除
            action = request.form.get('action')
            
            if action == 'add':
                target_model_id = request.form.get('target_model_id')
                dependency_type = request.form.get('dependency_type')
                dependency_strength = float(request.form.get('dependency_strength', 1.0))
                description = request.form.get('description')
                
                new_dep = Model_Dependency(
                    source_model_id=model_id,
                    target_model_id=target_model_id,
                    dependency_type=dependency_type,
                    dependency_strength=dependency_strength,
                    description=description
                )
                db.session.add(new_dep)
                db.session.commit()
                flash('依赖关系添加成功', 'success')
            
            elif action == 'delete':
                dep_id = request.form.get('dependency_id')
                dep = db.session.query(Model_Dependency).get(dep_id)
                if dep:
                    db.session.delete(dep)
                    db.session.commit()
                    flash('依赖关系删除成功', 'success')
        
        # 获取现有依赖关系
        dependencies = db.session.query(Model_Dependency).filter_by(
            source_model_id=model_id
        ).all()
        
        # 获取可选的目标模型
        available_models = db.session.query(Training_Model).filter(
            Training_Model.id != model_id
        ).all()
        
        return self.render_template('model_dependency_management.html',
                                  model=model,
                                  dependencies=dependencies,
                                  available_models=available_models)

    check_delete_permission = check_edit_permission

    import pysnooper
    @expose("/download/<model_id>", methods=["GET", 'POST'])
    # @pysnooper.snoop()
    def download_model(self, model_id):
        train_model = db.session.query(Training_Model).filter_by(id=model_id).first()
        if train_model.download_url:
            return redirect(train_model.download_url)
        if train_model.path:
            if 'http://' in train_model.path or 'https://' in train_model.path:
                return redirect(train_model.path)
            if '/mnt' in train_model.path:
                download_url = request.host_url + 'static/' + train_model.path.strip('/')
                return redirect(download_url)
        flash(__('未发现模型存储地址'),'warning')

        return redirect(conf.get('MODEL_URLS',{}).get('train_model','/frontend/'))


    @expose("/deploy/<model_id>", methods=["GET", 'POST'])
    def deploy(self, model_id):
        train_model = db.session.query(Training_Model).filter_by(id=model_id).first()
        exist_inference = db.session.query(InferenceService).filter_by(model_name=train_model.name).filter_by(model_version=train_model.version).first()
        from myapp.views.view_inferenceserving import InferenceService_ModelView_base
        inference_class = InferenceService_ModelView_base()
        inference_class.src_item_json = {}
        if not exist_inference:
            exist_inference = InferenceService()
            exist_inference.project_id = train_model.project_id
            exist_inference.project = train_model.project
            exist_inference.model_name = train_model.name
            exist_inference.label = train_model.describe[:100]
            exist_inference.model_version = train_model.version
            exist_inference.model_path = train_model.path
            exist_inference.service_type = train_model.api_type
            exist_inference.images = ''
            exist_inference.name = '%s-%s-%s' % (exist_inference.service_type, train_model.name, train_model.version.replace('v', '').replace('.', ''))
            inference_class.pre_add(exist_inference)

            db.session.add(exist_inference)
            db.session.commit()
            flash(__('新服务版本创建完成'), 'success')
        else:
            flash(__('服务版本已存在'), 'success')
        import urllib.parse

        url = conf.get('MODEL_URLS', {}).get('inferenceservice', '') + '?filter=' + urllib.parse.quote(json.dumps([{"key": "model_name", "value": exist_inference.model_name}], ensure_ascii=False))
        print(url)
        return redirect(url)


    # 划分数据历史版本
    def pre_list_res(self,res):
        data=res['data']
        import itertools
        all_data={item['id']:item for item in data}
        all_last_data_id=[]
        # 按name分组，最新数据下包含其他更老的数据作为历史集合
        data = sorted(data, key=lambda x: x['name'])
        for name, group in itertools.groupby(data, key=lambda x: x['name']):
            group=list(group)
            max_id = max([x['id'] for x in group])
            all_last_data_id.append(max_id)
            for item in group:
                if item['id']!=max_id:
                    if 'children' not in all_data[max_id]:
                        all_data[max_id]['children']=[all_data[item['id']]]
                    else:
                        all_data[max_id]['children'].append(all_data[item['id']])
        # 顶层只保留最新的数据
        res['data'] = [all_data[id] for id in all_data if id in all_last_data_id]
        return res

    @action("muldelete", "删除", "确定删除所选记录?", "fa-trash", single=False)
    def muldelete(self, items):
        return self._muldelete(items)

# 在文件顶部添加导入
from .base import MyappModelView

# 修改类定义（大约在第353行）
class Training_Model_ModelView(Training_Model_ModelView_Base, MyappModelView, MyappModelRestApi):
    datamodel = SQLAInterface(Training_Model)
    route_base = '/training_model_modelview/web/api'
    # add_columns = ['project', 'name', 'version', 'describe', 'path', 'framework', 'metrics','api_type']
    # 显式定义列配置以避免被其他基类覆盖
    list_columns = ['project_url', 'name', 'version', 'model_type', 'model_size', 
                   'model_metric', 'framework', 'api_type', 'pipeline_url',
                   'creator', 'modified', 'deploy', 'dependencies_count']
    
    show_columns = ['project_url', 'name', 'version', 'model_type', 'model_size', 
                   'model_metric', 'framework', 'api_type', 'pipeline_url',
                   'creator', 'modified', 'deploy', 'dependencies_count']
    
    def __init__(self):
        super().__init__()
        # 确保这些属性在实例化时不会被覆盖
        self.list_columns = ['project_url', 'name', 'version', 'model_type', 'model_size', 
                           'model_metric', 'framework', 'api_type', 'pipeline_url',
                           'creator', 'modified', 'deploy', 'dependencies_count']
        
        self.show_columns = ['project_url', 'name', 'version', 'model_type', 'model_size', 
                           'model_metric', 'framework', 'api_type', 'pipeline_url',
                           'creator', 'modified', 'deploy', 'dependencies_count']
    # 添加模板配置
    list_template = 'appbuilder/general/model/list.html'
    show_template = 'appbuilder/general/model/show.html'
    add_template = 'appbuilder/general/model/add.html'
    edit_template = 'appbuilder/general/model/edit.html'
    list_ui_type = 'table'
    list_ui_args = {
        'show_columns': ['project_url', 'name', 'version', 'model_type', 'model_size', 
                         'model_metric', 'framework', 'api_type', 'pipeline_url',
                         'creator', 'modified', 'deploy', 'dependencies_count']
    }


appbuilder.add_api(Training_Model_ModelView)


class Training_Model_ModelView_Api(Training_Model_ModelView_Base, MyappModelRestApi):  # noqa
    datamodel = SQLAInterface(Training_Model)
    # base_order = ('id', 'desc')
    route_base = '/training_model_modelview/api'
    
    # @expose("/batch_analyze_dependencies", methods=["POST"])
    # def batch_analyze_dependencies(self):
    #     """批量分析模型依赖关系API"""
    #     model_ids = request.form.get('model_ids', '').split(',')
    #     if not model_ids or model_ids == ['']:
    #         return self.response(400, message="请选择至少一个模型")
        
    #     analyzer = ModelDependencyAnalyzer()
    #     success_count = 0
    #     error_count = 0
        
    #     for model_id in model_ids:
    #         try:
    #             if model_id.strip():  # 确保model_id不为空
    #                 analyzer.sync_dependencies_to_db(int(model_id.strip()))
    #                 success_count += 1
    #         except Exception as e:
    #             error_count += 1
    #             print(f"分析模型ID {model_id} 依赖关系失败: {e}")
        
    #     message = f'批量分析完成: 成功 {success_count} 个，失败 {error_count} 个'
    #     return self.response(200, message=message)
    
    @expose('/action/view_dependency_graph/<int:model_id>')
    def view_dependency_graph_api(self, model_id):
        """获取模型依赖关系图数据"""
        try:
            print(f"DEBUG: 接收到依赖图请求，模型ID: {model_id}")
            
            from myapp.models.model_dependency import Model_Dependency
            from sqlalchemy import or_
            
            # 检查模型是否存在
            model = db.session.query(Training_Model).filter_by(id=model_id).first()
            if not model:
                print(f"DEBUG: 模型 {model_id} 不存在")
                return self.response(404, message=f"模型 {model_id} 不存在")
            
            print(f"DEBUG: 找到模型: {model.name}")
            
            dependencies = db.session.query(Model_Dependency).filter(
                or_(Model_Dependency.source_model_id == model_id,
                    Model_Dependency.target_model_id == model_id)
            ).all()
            
            print(f"DEBUG: 找到 {len(dependencies)} 个依赖关系")
            
            nodes = []
            edges = []
            
            if dependencies:
                for dep in dependencies:
                    try:
                        # 构建节点和边的数据结构
                        source_label = dep.source_model.name if dep.source_model else f"Model {dep.source_model_id}"
                        source_type = dep.source_model.model_type if dep.source_model else "unknown"
                        
                        nodes.append({
                            'id': dep.source_model_id,
                            'label': source_label,
                            'type': source_type
                        })
                        
                        target_label = dep.target_model.name if dep.target_model else f"Model {dep.target_model_id}"
                        target_type = dep.target_model.model_type if dep.target_model else "unknown"
                        
                        # 添加目标节点（如果不重复）
                        if not any(n['id'] == dep.target_model_id for n in nodes):
                            nodes.append({
                                'id': dep.target_model_id,
                                'label': target_label,
                                'type': target_type
                            })
                        
                        edges.append({
                            'source': dep.source_model_id,
                            'target': dep.target_model_id,
                            'type': dep.dependency_type,
                            'strength': dep.dependency_strength
                        })
                    except Exception as e:
                        print(f"DEBUG: 处理依赖关系时出错: {e}")
                        continue
            else:
                # 如果没有依赖关系，至少返回当前模型作为节点
                nodes.append({
                    'id': model.id,
                    'label': model.name,
                    'type': model.model_type if hasattr(model, 'model_type') else 'unknown'
                })
            
            result = {'nodes': nodes, 'edges': edges}
            print(f"DEBUG: 返回结果: {result}")
            
            return self.response(200, data=result)
            
        except Exception as e:
            print(f"DEBUG: API调用出错: {e}")
            import traceback
            traceback.print_exc()
            return self.response(500, message=f"获取依赖图失败: {str(e)}")
    
    @expose('/action/view_version_timeline/<int:model_id>')
    def view_version_timeline_api(self, model_id):
        """获取模型版本时间线"""
        try:
            # 检查模型是否存在
            model = db.session.query(Training_Model).filter_by(id=model_id).first()
            if not model:
                return self.response(404, message=f"模型 {model_id} 不存在")
            
            # 由于我们还没有Model_Version表，我们模拟版本历史数据
            # 实际项目中，这里应该查询真实的版本历史表
            timeline_data = []
            
            # 查询同名模型的所有版本（按版本号升序）
            all_versions = db.session.query(Training_Model).filter_by(
                name=model.name
            ).order_by(Training_Model.created_on.asc()).all()
            
            if len(all_versions) > 1:
                # 有多个版本
                for i, version_model in enumerate(all_versions):
                    is_current = version_model.id == model.id
                    # 根据版本号判断版本类型
                    version_type = 'patch'
                    if 'v1.' in version_model.version or '.0.' in version_model.version:
                        version_type = 'major'
                    elif '.1' in version_model.version or '.2' in version_model.version:
                        version_type = 'minor'
                    if is_current:
                        version_type = 'current'
                    
                    timeline_data.append({
                        'id': version_model.id,
                        'version': version_model.version,
                        'date': version_model.created_on.isoformat() if version_model.created_on else None,
                        'type': version_type,
                        'changelog': version_model.describe or f'版本 {version_model.version} 发布',
                        'performance': {},
                        'metrics': version_model.metrics,
                        'is_current': is_current,
                        'framework': version_model.framework,
                        'model_type': version_model.model_type,
                        'creator': version_model.created_by.username if version_model.created_by else '未知',
                        'path': version_model.path
                    })
            else:
                # 只有当前版本
                timeline_data.append({
                    'id': model.id,
                    'version': model.version,
                    'date': model.created_on.isoformat() if model.created_on else None,
                    'type': 'current',
                    'changelog': f'当前版本: {model.describe or "首个版本"}',
                    'performance': {},
                    'metrics': model.metrics,
                    'is_current': True,
                    'framework': model.framework,
                    'model_type': model.model_type,
                    'creator': model.created_by.username if model.created_by else '未知',
                    'path': model.path
                })
            
            # 添加模型基本信息
            model_info = {
                'id': model.id,
                'name': model.name,
                'current_version': model.version,
                'framework': model.framework,
                'model_type': model.model_type,
                'description': model.describe,
                'created_on': model.created_on.isoformat() if model.created_on else None,
                'updated_on': model.changed_on.isoformat() if model.changed_on else None,
                'creator': model.created_by.username if model.created_by else '未知'
            }
            
            return self.response(200, data={
                'timeline': timeline_data,
                'model_info': model_info,
                'statistics': {
                    'total_versions': len(timeline_data),
                    'version_types': list(set([item['type'] for item in timeline_data]))
                }
            })
            
        except Exception as e:
            import traceback
            error_details = traceback.format_exc()
            print(f"获取版本时间线失败: {error_details}")
            return self.response(500, message=f"获取版本时间线失败: {str(e)}")
    
    # @expose('/action/batch_dependency_graph/<model_ids>', methods=['GET'])
    # def batch_dependency_graph_api(self, model_ids):
    #     """获取多个模型的依赖关系图数据"""
    #     from myapp.models.model_dependency import Model_Dependency
    #     from sqlalchemy import or_
        
    #     try:
    #         ids = [int(id.strip()) for id in model_ids.split(',') if id.strip().isdigit()]
            
    #         # 查询所有相关的依赖关系
    #         dependencies = db.session.query(Model_Dependency).filter(
    #             or_(Model_Dependency.source_model_id.in_(ids),
    #                 Model_Dependency.target_model_id.in_(ids))
    #         ).all()
            
    #         # 构建图数据
    #         nodes = set()
    #         edges = []
            
    #         for dep in dependencies:
    #             nodes.add(dep.source_model_id)
    #             nodes.add(dep.target_model_id)
    #             edges.append({
    #                 'source': dep.source_model_id,
    #                 'target': dep.target_model_id,
    #                 'type': dep.dependency_type
    #             })
            
    #         # 获取节点详细信息
    #         models = db.session.query(Training_Model).filter(
    #             Training_Model.id.in_(list(nodes))
    #         ).all()
            
    #         node_data = [{
    #             'id': model.id,
    #             'name': model.name,
    #             'version': model.version,
    #             'type': model.model_type,
    #             'selected': model.id in ids  # 标记哪些是用户选中的
    #         } for model in models]
            
    #         return self.response(200, data={
    #             'nodes': node_data,
    #             'edges': edges
    #         })
            
    #     except Exception as e:
    #         return self.response(500, message=str(e))
    
    @expose('/action/analyze_dependencies/<int:model_id>', methods=['POST'])
    def analyze_dependencies_api(self, model_id):
        """API: 分析模型依赖关系"""
        try:
            analyzer = ModelDependencyAnalyzer()
            analyzer.sync_dependencies_to_db(model_id)
            return self.response(200, message="依赖关系分析完成")
        except Exception as e:
            return self.response(500, message=f"分析失败: {str(e)}")
    
    @expose('/action/dependency_types')
    def get_dependency_types_api(self):
        """获取依赖类型列表"""
        types = [
            {'value': 'model_inheritance', 'label': '模型继承'},
            {'value': 'pipeline_flow', 'label': 'Pipeline流程'},
            {'value': 'shared_dataset', 'label': '共享数据集'},
            {'value': 'version_evolution', 'label': '版本演进'},
            {'value': 'manual', 'label': '手动配置'}
        ]
        return self.response(200, data=types)
    
    @expose('/action/dependency_statistics/<int:model_id>')
    def get_dependency_statistics_api(self, model_id):
        """获取依赖关系统计信息"""
        from myapp.models.model_dependency import Model_Dependency
        
        # 统计不同类型的依赖关系数量
        stats = db.session.query(
            Model_Dependency.dependency_type,
            db.func.count(Model_Dependency.id).label('count')
        ).filter_by(source_model_id=model_id).group_by(
            Model_Dependency.dependency_type
        ).all()
        
        result = [{'type': stat[0], 'count': stat[1]} for stat in stats]
        return self.response(200, data=result)


appbuilder.add_api(Training_Model_ModelView_Api)

class Training_Model_FlowiseApi(MyappModelRestApi):
    """专门为Flowise集成设计的API"""
    datamodel = SQLAInterface(Training_Model)
    base_permissions = ['can_list', 'can_show']
    
    # 添加正确的路由基础路径
    route_base = '/api/v1/training_model_flowise'
    
    # 只允许读取操作，确保安全
    class_permission_name = 'Training_Model_Flowise'
    method_permission_name = {
        'get_list': 'can_list',
        'get': 'can_show',
        'models': 'can_list',
        'model_detail': 'can_show'
    }
    
    base_filters = [['id', Training_Model_Filter, lambda: []]]
    
    def __init__(self):
        """确保datamodel正确初始化"""
        super().__init__()
        if not hasattr(self, 'datamodel') or self.datamodel is None:
            self.datamodel = SQLAInterface(Training_Model)
    
    @expose('/models', methods=['GET'])
    def models(self):
        """获取模型列表 - Flowise兼容格式"""
        try:
            # 确保datamodel已正确初始化
            if not hasattr(self, 'datamodel') or self.datamodel is None:
                self.datamodel = SQLAInterface(Training_Model)
            
            # 直接使用数据库会话查询，避免使用joinedload预加载可能为None的关联对象
            # 构建基础查询
            query = db.session.query(Training_Model)
            
            # 移除joinedload，改为在使用时进行安全访问
            # query = query.options(
            #     joinedload(Training_Model.created_by),
            #     joinedload(Training_Model.project)
            # )
            
            # 手动应用权限过滤，避免使用可能有问题的过滤器
            user_roles = [role.name.lower() for role in list(g.user.roles)] if g.user else []
            if "admin" not in user_roles:
                # 非管理员用户只能看到自己项目的模型
                join_projects_id = security_manager.get_join_projects_id(db.session)
                if join_projects_id:
                    query = query.filter(Training_Model.project_id.in_(join_projects_id))
                else:
                    # 如果没有项目权限，返回空列表
                    query = query.filter(Training_Model.id == -1)  # 不存在的ID
            
            # 执行查询
            models = query.all()
            
            # 转换为Flowise期望的格式
            model_list = []
            for model in models:
                try:
                    # 安全访问关联对象，避免AttributeError
                    creator_username = None
                    if hasattr(model, 'created_by') and model.created_by:
                        creator_username = getattr(model.created_by, 'username', None)
                    
                    project_name = None
                    if hasattr(model, 'project') and model.project:
                        project_name = getattr(model.project, 'name', None)
                    
                    model_data = {
                        'id': str(model.id),
                        'name': model.name,
                        'version': model.version,
                        'model_type': model.model_type or 'base',
                        'framework': model.framework,
                        'api_type': model.api_type,
                        'path': model.path,
                        'describe': model.describe or '',
                        'created_on': model.created_on.isoformat() if model.created_on else None,
                        'modified': model.changed_on.isoformat() if model.changed_on else None,
                        'creator': getattr(model.created_by, 'username', None) if model.created_by else None,
                        'project': getattr(model.project, 'name', None) if model.project else None,
                        'military_concept_desc': model.military_concept_desc or '',
                        'model_size': getattr(model, 'model_size', None),
                        'model_metric': getattr(model, 'model_metric', '') or '',
                        'pipeline_url': f'/pipeline_modelview/web/{model.pipeline_id}' if getattr(model, 'pipeline_id', None) and str(model.pipeline_id) != '0' else 'unknown',
                        'project_url': f'/project_modelview/web/{model.project.id}' if model.project and hasattr(model.project, 'id') else 'public(公共项目)',
                        # Flowise特定字段
                        'status': 'available',
                        'endpoint': f'/api/v1/models/{model.id}/predict',
                        'input_schema': self._get_model_input_schema(model),
                        'output_schema': self._get_model_output_schema(model)
                    }
                    model_list.append(model_data)
                except Exception as e:
                    print(f"处理模型 {getattr(model, 'id', 'unknown')} 时出错: {str(e)}")
                    continue
            
            from flask import jsonify
            return jsonify({
                'status': 'success',
                'data': model_list,
                'total': len(model_list)
            })
            
        except Exception as e:
            print(f"获取模型列表失败: {str(e)}")
            import traceback
            traceback.print_exc()
            from flask import jsonify
            return jsonify({
                'status': 'error',
                'message': f'获取模型列表失败: {str(e)}',
                'data': []
            }), 500
    
    @expose('/models/<model_id>', methods=['GET'])
    def model_detail(self, model_id):
        """获取单个模型详情 - Flowise兼容格式"""
        try:
            model = self.datamodel.get(model_id)
            if not model:
                return self.response(404, message="模型不存在")
            
            # 检查权限
            if not self._check_model_permission(model):
                return self.response(403, message="无权限访问此模型")
            
            model_data = {
                'id': str(model.id),
                'name': model.name,
                'version': model.version,
                'model_type': model.model_type or 'base',
                'framework': model.framework,
                'api_type': model.api_type,
                'path': model.path,
                'dll_path': model.dll_path,
                'describe': model.describe or '',
                'metrics': model.metrics,
                'md5': model.md5,
                'run_id': model.run_id,
                'run_time': model.run_time,
                'pipeline_id': model.pipeline_id,
                'tags': model.tags,
                'training_dataset': model.training_dataset,
                'model_config': model.model_config,
                'dependencies': model.dependencies,
                'military_concept_desc': model.military_concept_desc or '',
                'created_on': model.created_on.isoformat() if model.created_on else None,
                'modified': model.changed_on.isoformat() if model.changed_on else None,
                'creator': getattr(model.created_by, 'username', None) if model.created_by else None,
                'project': getattr(model.project, 'name', None) if model.project else None,
                'model_size': model.model_size,
                'model_metric': model.model_metric or '',
                # Flowise特定字段
                'status': 'available',
                'endpoint': f'/api/v1/models/{model.id}/predict',
                'health_check': f'/api/v1/models/{model.id}/health',
                'input_schema': self._get_model_input_schema(model),
                'output_schema': self._get_model_output_schema(model),
                'deployment_info': self._get_deployment_info(model)
            }
            
            return self.response(200, data=model_data)
            
        except Exception as e:
            import traceback
            error_details = traceback.format_exc()
            print(f"获取模型详情失败: {error_details}")
            return self.response(500, message=f"获取模型详情失败: {str(e)}")
    
    @expose('/models/<model_id>/predict', methods=['POST'])
    def predict(self, model_id):
        """模型推理接口 - 供Flowise调用"""
        try:
            model = self.datamodel.get(model_id)
            if not model:
                return self.response(404, message="模型不存在")
            
            # 检查权限
            if not self._check_model_permission(model):
                return self.response(403, message="无权限访问此模型")
            
            # 获取请求数据
            request_data = request.get_json()
            if not request_data:
                return self.response(400, message="请求数据不能为空")
            
            # 根据模型类型调用相应的推理服务
            result = self._call_model_inference(model, request_data)
            
            return self.response(200, data={
                'model_id': model_id,
                'model_name': model.name,
                'prediction': result,
                'timestamp': datetime.datetime.now().isoformat()
            })
            
        except Exception as e:
            import traceback
            error_details = traceback.format_exc()
            print(f"模型推理失败: {error_details}")
            return self.response(500, message=f"模型推理失败: {str(e)}")
    
    @expose('/models/<model_id>/health', methods=['GET'])
    def health_check(self, model_id):
        """模型健康检查"""
        try:
            model = self.datamodel.get(model_id)
            if not model:
                return self.response(404, message="模型不存在")
            
            # 检查权限
            if not self._check_model_permission(model):
                return self.response(403, message="无权限访问此模型")
            
            # 检查模型服务状态
            health_status = self._check_model_health(model)
            
            return self.response(200, data={
                'model_id': model_id,
                'model_name': model.name,
                'status': health_status['status'],
                'details': health_status['details'],
                'timestamp': datetime.datetime.now().isoformat()
            })
            
        except Exception as e:
            return self.response(500, message=f"健康检查失败: {str(e)}")
    
    def _check_model_permission(self, model):
        """检查模型访问权限"""
        user_roles = [role.name.lower() for role in list(g.user.roles)]
        if "admin" in user_roles:
            return True
        
        join_projects_id = security_manager.get_join_projects_id(db.session)
        return model.project_id in join_projects_id
    
    def _get_model_input_schema(self, model):
        """获取模型输入schema"""
        # 根据模型类型返回相应的输入schema
        if model.api_type == 'tfserving':
            return {
                'type': 'object',
                'properties': {
                    'instances': {
                        'type': 'array',
                        'description': 'TensorFlow Serving input instances'
                    }
                }
            }
        elif model.api_type == 'torch-server':
            return {
                'type': 'object',
                'properties': {
                    'data': {
                        'type': 'array',
                        'description': 'PyTorch model input data'
                    }
                }
            }
        else:
            return {
                'type': 'object',
                'properties': {
                    'input': {
                        'type': 'array',
                        'description': 'Model input data'
                    }
                }
            }
    
    def _get_model_output_schema(self, model):
        """获取模型输出schema"""
        return {
            'type': 'object',
            'properties': {
                'predictions': {
                    'type': 'array',
                    'description': 'Model predictions'
                },
                'confidence': {
                    'type': 'number',
                    'description': 'Prediction confidence'
                }
            }
        }
    
    def _get_deployment_info(self, model):
        """获取模型部署信息"""
        # 查询相关的推理服务
        inference_services = db.session.query(InferenceService).filter_by(
            model_name=model.name
        ).all()
        
        deployments = []
        for service in inference_services:
            deployments.append({
                'service_id': service.id,
                'service_name': service.name,
                'status': service.model_status,
                'endpoint': service.host,
                'created_on': service.created_on.isoformat() if service.created_on else None
            })
        
        return {
            'deployments': deployments,
            'total_deployments': len(deployments)
        }
    
    def _call_model_inference(self, model, request_data):
        """调用模型推理"""
        # 查找对应的推理服务
        inference_service = db.session.query(InferenceService).filter_by(
            model_name=model.name,
            model_status='online'
        ).first()
        
        if not inference_service:
            raise Exception(f"模型 {model.name} 没有可用的推理服务")
        
        # 根据API类型调用相应的推理接口
        import requests
        
        # 获取服务的访问地址
        service_host = inference_service.host
        
        if model.api_type == 'tfserving':
            url = f"{service_host}/v1/models/{model.name}:predict"
            response = requests.post(url, json=request_data, timeout=30)
        elif model.api_type == 'torch-server':
            url = f"{service_host}/predictions/{model.name}"
            response = requests.post(url, json=request_data, timeout=30)
        else:
            # 通用推理接口
            url = f"{service_host}/predict"
            response = requests.post(url, json=request_data, timeout=30)
        
        if response.status_code == 200:
            return response.json()
        else:
            raise Exception(f"推理服务返回错误: {response.status_code} - {response.text}")
    
    def _check_model_health(self, model):
        """检查模型健康状态"""
        try:
            # 查找对应的推理服务
            inference_service = db.session.query(InferenceService).filter_by(
                model_name=model.name
            ).first()
            
            if not inference_service:
                return {
                    'status': 'unavailable',
                    'details': '没有找到对应的推理服务'
                }
            
            if inference_service.model_status != 'online':
                return {
                    'status': 'unhealthy',
                    'details': f'推理服务状态: {inference_service.model_status}'
                }
            
            # 尝试ping推理服务
            import requests
            try:
                health_url = f"{inference_service.host}/health"
                response = requests.get(health_url, timeout=5)
                if response.status_code == 200:
                    return {
                        'status': 'healthy',
                        'details': '推理服务运行正常'
                    }
                else:
                    return {
                        'status': 'unhealthy',
                        'details': f'健康检查失败: {response.status_code}'
                    }
            except requests.RequestException as e:
                return {
                    'status': 'unhealthy',
                    'details': f'无法连接到推理服务: {str(e)}'
                }
                
        except Exception as e:
            return {
                'status': 'error',
                'details': f'健康检查异常: {str(e)}'
            }
appbuilder.add_api(Training_Model_FlowiseApi)
